ECMAScript 6(下面简称ES6)增强了对面向对象的支持,引入了class关键字,并且为类的创建、继承提供了简洁清晰的语法。
类声明与类表达式
毋庸置疑,class关键字就是用来定义类的。
定义类的常用方式是类声明,语法如下:
1 2 3 4 5 6 7 8 9 10 11
| class ClassName { constructor() { } method() { } }
|
而另一种方式则是类表达式,语法如下:
1 2 3 4 5 6 7 8 9 10 11
| var ClassName = class { constructor() { } method() { } };
|
下面通过class关键字重新实现Shape类(注意,class关键字目前只在Chrome42的严格模式下可用):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 'use strict'; class Shape { constructor(name) { this.setName(name || '形状'); } getName() { return this._name; } setName(name) { this._name = name; } perimeter() { } } var shape = new Shape(); shape.setName('我的形状'); shape.getName();
|
Getter和Setter
getName、setName这两个方法本来是为了隐藏_name属性而存在的,但调用方法明显比访问属性更累赘一些。Getter和Setter就是为了解决这个问题而出现的。它们分别用于定义对属性读和写时进行的操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class Shape { constructor(name) { this.name = name || '形状'; } get name() { return this._name; } set name(name) { this._name = name; } get perimeter() { } } var shape = new Shape(); shape.name = '我的形状'; console.log(shape.name); shape.perimeter = 10;
|
Getter和Setter分别通过get、set关键字来声明,二者可以同时存在或只存在其一。如果一个属性只有getter没有setter,那它就是个只读属性(如perimeter)。
继承
与Java一样,Javascript也使用extends关键字实现继承,子类中可以通过super关键字调用父类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class Square extends Shape { constructor() { this.length = 0; super('正方形'); } get length() { return this._length; } set length(length) { if (length < 0) { throw new Error('...'); } this._length = length; } get perimeter() { return this.length * 4; } } var square = new Square();
|
上面的代码看起来没问题,但实例化Square的时候会出现异常:
1
| Uncaught ReferenceError: this is not defined
|
这可奇怪了,this明明指的是当前对象,为何提示未定义呢?进一步查资料得知,子类必须在调用父类构造函数后才能使用this。于是Square类代码调整为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class Square extends Shape { constructor() { super('正方形'); this.length = 0; } get length() { return this._length; } set length(length) { if (length < 0) { throw new Error('...'); } this._length = length; } get perimeter() { return this.length * 4; } }
|
接着声明长方形类和圆形类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class Rectangle extends Shape { constructor() { super('矩形'); this.length = 0; this.width = 0; } get length() { return this._length; } set length(length) { if (length < 0) { throw new Error('...'); } this._length = length; } get width() { return this._width; } set width(width) { if (width < 0) { throw new Error('...'); } this._width = width; } get perimeter() { return (this.length + this.width) * 2; } }
|
或者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class Circle extends Shape { constructor() { super('圆形'); this.radius = 0; } get radius() { return this._radius; } set radius(radius) { if (radius < 0) { throw new Error('...'); } this._radius = radius; } get perimeter() { return 2 * Math.PI * this.radius; } }
|
最后是计算周长:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function computePerimeter(shape) { console.log(shape.name + '的周长是' + shape.perimeter); } var rectangle = new Rectangle(); rectangle.width = 10; rectangle.length = 20; computePerimeter(rectangle); var square = new Square(); square.length = 10; computePerimeter(square); var circle = new Circle(); circle.radius = 10; computePerimeter(circle);
|
其他细节
语法糖
虽然引入了class关键字,但ECMAScript 6并没有真的引入类这个概念,通过class定义的仍然是函数:
1
| console.log(typeof Shape);
|
所以说,class仅仅是通过更简单直观的语法去实现原型链继承。这种对语言功能没有影响、但是给程序员带来方便的新语法,被称为语法糖。
变量提升
Javascript中的函数声明存在变量提升这一特性,也就是说,调用函数的代码可以在函数声明之前。例如:
虽然通过class声明的“类”实际上也是函数,但是它没有变量提升这一特性。例如下面这段代码会抛出异常:
1 2
| var a = new A(); class A { }
|
最后
可见,与原型链写法比起来,使用class定义类简单得多。但是要想在ES6普及之前使用这个语法,只能通过一些工具把ES6转成ES5或ES3了。